Skip to content

ci(dx): guard public dev-facing functions have a JSDoc summary#715

Merged
blove merged 2 commits into
mainfrom
ci/dx-coverage-guard
Jun 20, 2026
Merged

ci(dx): guard public dev-facing functions have a JSDoc summary#715
blove merged 2 commits into
mainfrom
ci/dx-coverage-guard

Conversation

@blove

@blove blove commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What

A CI guard that keeps the TypeScript DX bar (from the v0.0.52 audit) from regressing: scripts/check-dx-coverage.mjs does a TypeDoc walk over the @threadplane/chat/ag-ui/langgraph/render public surfaces and fails when an exported function lacks a JSDoc summary.

  • Escape hatch: @internal-tagged symbols are exempt (spec-only / implementation exports).
  • Wiring: runs as a step in the existing Library — lint / test / build job (no new job, no extra runner).
  • Scope (v1): summary presence on functions. @example/param-doc enforcement can tighten later.

Result

Found and fixed the one straggler on main (extractErrorMessage — documented as a public utility). Guard now reports:

✓ DX-coverage: all 65 public functions across chat/ag-ui/langgraph/render have a JSDoc summary.

Verification

node scripts/check-dx-coverage.mjs → green locally; ci.yml YAML validated; chat builds green.

🤖 Generated with Claude Code

Adds scripts/check-dx-coverage.mjs — a TypeDoc walk over the chat/ag-ui/
langgraph/render public surfaces that fails when an exported function lacks a
JSDoc summary (so the audited DX bar can't silently regress). Honors @internal
as an escape hatch for spec-only/implementation exports. Wired as a step in the
`library` CI job.

Documented the one straggler it found (extractErrorMessage). Guard reports:
"all 65 public functions across chat/ag-ui/langgraph/render have a JSDoc summary."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@blove blove enabled auto-merge (squash) June 20, 2026 01:50
@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
threadplane Ignored Ignored Preview Jun 20, 2026 1:52am

Request Review

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 2m 40s —— View job


Review: ci(dx) — DX-coverage guard

  • Gather context and read changed files
  • Review scripts/check-dx-coverage.mjs
  • Review .github/workflows/ci.yml changes
  • Review chat-error.component.ts changes
  • Post findings

Overall: solid addition. The concept is right, CI wiring is minimal and correct, and the extractErrorMessage JSDoc is accurate. Three issues worth addressing before merge:

scripts/check-dx-coverage.mjs

Medium — silent green at checked === 0 (inline comment here)
If TypeDoc converts all entry points successfully but emits zero Function reflections (e.g. after a future shift to arrow-function exports), the script prints "all 0 public functions" and exits 0 — the guard certifies nothing. Should add a floor check: if checked === 0 and no entry-point error was recorded, treat it as a misconfiguration and exit(1).

Low — app.options.addReader(new TSConfigReader()) is dead code (inline comment here)
bootstrapWithPlugins runs the full option pipeline before it returns; adding a reader post-bootstrap has no effect on convert(). The tsconfig path is already passed explicitly on the line above. Safe to remove.

Low — arrow-function exports escape the guard (inline comment here)
TypeDoc maps export const foo = () => {} to ReflectionKind.Variable, so those symbols are skipped silently. No such exports exist in the four covered libraries today, but the gap will bite silently if one is added. At minimum a comment noting the known limitation would help.

.github/workflows/ci.yml

No issues. Step is appended to the correct job, runs after build (consistent with the other scripts), no injection risk.

libs/chat/src/lib/primitives/chat-error/chat-error.component.ts

No issues. The JSDoc on extractErrorMessage is accurate and complete — summary, @param, @returns, and @example all present.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 1m 31s —— View job


PR Review

  • Gather context
  • Review scripts/check-dx-coverage.mjs
  • Review .github/workflows/ci.yml changes
  • Review chat-error.component.ts changes
  • Post findings

Overall this is a clean, well-scoped CI guard. Three minor observations:

1. Redundant TSConfigReader (dead code) — scripts/check-dx-coverage.mjs:66

const app = await Application.bootstrapWithPlugins({ ... });
app.options.addReader(new TSConfigReader()); // ← no-op

Application.bootstrapWithPlugins adds a TSConfigReader internally before it returns. Adding another one afterward has no effect in TypeDoc v0.28 because options have already been resolved at that point. Safe to remove. Fix this →

2. Redundant isInternal guard — scripts/check-dx-coverage.mjs:72

const app = await Application.bootstrapWithPlugins({
  excludeInternal: true, // ← already strips @internal symbols
  ...
});
// ...
if (isInternal(ref, ref.signatures?.[0])) continue; // ← never true

Because excludeInternal: true is passed to TypeDoc, @internal-tagged reflections are stripped from the project before walk ever sees them. The isInternal check is defensive but misleading — it implies TypeDoc might still surface @internal symbols, which it won't. Fine to keep as belt-and-suspenders, but worth a note if someone is debugging the script later.

3. Return description left empty in api-docs.json

The diff updates description and params[0].description for extractErrorMessage but leaves returns.description as "". Minor inconsistency given the otherwise thorough doc update — a follow-up is fine.


No correctness bugs, security issues, or type-safety problems found. The CI step has no shell injection risk (hardcoded run: string, no ${{ }} interpolation). The extractErrorMessage JSDoc is accurate and well-formed. The guard correctly fires in the existing Library job after the build step.

@blove blove merged commit f0c06a3 into main Jun 20, 2026
7 checks passed
process.exit(1);
}

console.log(`✓ DX-coverage: all ${checked} public functions across chat/ag-ui/langgraph/render have a JSDoc summary.`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent pass when checked === 0 — if TypeDoc converts successfully but finds no Function reflections (e.g. after a future refactor to arrow-function exports), this line prints "all 0 public functions" and exits 0, certifying nothing.

Add a floor check so a zero-function result is treated as a misconfiguration:

Suggested change
console.log(`✓ DX-coverage: all ${checked} public functions across chat/ag-ui/langgraph/render have a JSDoc summary.`);
if (!violations.length && checked === 0 && process.exitCode !== 1) {
console.error('✗ DX-coverage: no public functions found — check entry points or entryPointStrategy.');
process.exit(1);
}
if (violations.length) {

Fix this →

excludeInternal: true,
...(libTsconfig ? { tsconfig: libTsconfig } : {}),
});
app.options.addReader(new TSConfigReader());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.options.addReader(new TSConfigReader()) is dead code here — bootstrapWithPlugins already runs the full option-reading pipeline (including TSConfig loading) before it returns. Adding a reader after bootstrap has no effect because app.convert() doesn't re-read options. The tsconfig path is already explicitly threaded in via the tsconfig key above (line 64), so this line can be removed without changing behaviour.

Fix this →

if (!project) throw new Error(`TypeDoc failed to convert ${lib.slug}`);

for (const ref of walk(project.children)) {
if (ref.kind !== ReflectionKind.Function) continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrow-function exports escape the guard. TypeDoc classifies export const foo = () => {} as ReflectionKind.Variable, not ReflectionKind.Function, so it's skipped here. No exported arrow functions exist in the four covered libraries today (verified), but the guard will silently not apply if one is added.

If you want to close this gap now or in a follow-up:

Suggested change
if (ref.kind !== ReflectionKind.Function) continue;
if (ref.kind !== ReflectionKind.Function && ref.kind !== ReflectionKind.Variable) continue;
// Variables: only check if callable (has call signatures) – catches arrow-function exports
if (ref.kind === ReflectionKind.Variable && !ref.type?.declaration?.signatures?.length) continue;

Or at minimum add a comment noting the known limitation so future contributors know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant